
import { DEBUG } from "cc/env";
import { Resource } from "./Resource";
import { Macro } from "../Main/Macros";
import c3d from "../tool/cc";
import UIView from "./ui/UIView";

class ResourceCache {
    /**缓存资源的map */
    private _caches = new Map<string, Resource.CacheData>();
    private name = Macro.UNKNOWN;

    constructor(name: string) {
        this.name = name;
    }

    /**获取资源缓存的数量 */
    get size() { return this._caches.size; }

    print(delegate: CacheManagerPrintDelegate<Map<string, Resource.CacheData>, Map<string, CacheInfo>>) {
        delegate.printLocal(this._caches, this.name);
    }

    /**
     * 获取资源缓存
     * @param path 资源path
     * @param isCheck 是否检测资源有效性
     * @returns 
     */
    get(path: string, isCheck: boolean) {
        let cache = this._caches.get(path);
        if (isCheck && cache && cache.isInvalid) {
            //资源已经释放
            c3d.warn(`资源加载完成，但已经被释放 , 重新加载资源 : ${path}`);
            this.remove(path);
            return null;
        }
        return cache;
    }

    /**
     * 设置资源 
     * @param path 资源path
     * @param data 数据 
     */
    set(path: string, data: Resource.CacheData) {
        this._caches.set(path, data);
    }

    /**
     * 移除缓存资源 
     * @param path 资源path
     * @returns 
     */
    remove(path: string) {
        return this._caches.delete(path);
    }

    /**移除没有使用的资源缓存 */
    removeUnuseCaches() {
        this._caches.forEach((value, key, origin) => {
            if (Array.isArray(value.data)) {
                let isAllDelete = true;
                for (let i = 0; i < value.data.length; i++) {
                    if (value.data[i] && value.data[i].refCount > 0) {
                        isAllDelete = false;
                    }
                }
                if (isAllDelete) {
                    this._caches.delete(key);
                    if (DEBUG) c3d.log(`删除不使用的资源目录 bundle : ${this.name} dir : ${key}`);
                }
            } else {
                if (value.data && value.data.refCount <= 0) {
                    this._caches.delete(key);
                    if (DEBUG) c3d.log(`删除不使用的资源 bundle : ${this.name} url : ${key}`);
                }
            }
        });
    }
}

class CacheInfo {
    refCount = 0;
    url: string = "";
    /**是否常驻于内存中 */
    retain: boolean = false;

    constructor(url?: string) {
        this.url ??= url;
    }
}

class RemoteCaches {
    /**缓存资源map */
    private _caches = new Map<string, Resource.CacheData>();
    /**图片缓存map */
    private _spriteFrameCaches = new Map<string, Resource.CacheData>();
    /**资源信息 */
    private _resMap = new Map<string, CacheInfo>();
    /**
     * 获取远程缓存数据
     * @param url 远程地址
     */
    get(url: string) { return this._caches.get(url); }

    /**
     * 获取缓存的图片
     * @param url 图片url
     * @returns 
     */
    getSpriteFrame(url: string) {
        if (this._spriteFrameCaches.has(url)) {
            let cache = this._spriteFrameCaches.get(url);
            let texture2D = this.get(url);
            if (texture2D) {
                return cache;
            } else {
                this.remove(url);//缓存资源失效，删除
            }
        }
    }

    /**
     * 设置图片资源缓存
     * @param url 图片url
     * @param data 数据
     * @returns 
     */
    setSpriteFrame(url: string, data: any) {
        if (data && data instanceof c3d.ImageAsset) {
            //同一图片加载两次也会回调到这里，这里如果当前精灵缓存中有，不在重新创建
            let spriteFrame = this.getSpriteFrame(url);
            if (spriteFrame) {
                return <c3d.SpriteFrame>(spriteFrame.data);
            }
            let sp = new c3d.SpriteFrame();
            let texture = new c3d.Texture2D();
            texture.image = data
            sp.texture = texture;

            //创建缓存信息
            let cache = new Resource.CacheData({ path: url, data: sp });
            cache.isLoaded = true;
            this._spriteFrameCaches.set(url, cache);
            return <c3d.SpriteFrame>(cache.data);
        }
    }

    /**
     * 设置资源缓存 url
     * @param url 资源url
     * @param data 资源数据
     */
    set(url: string, data: Resource.CacheData) {
        data.info.url = url;
        this._caches.set(url, data);
    }

    /**
     * 根据资源查找缓存信息，如果没有查找到默认创建一个
     * @param info 资源
     * @param isNoFoundCreate 没有查找到是否创建
     * @returns 
     */
    private _getCacheInfo(info: Resource.Info, isNoFoundCreate: boolean = true) {
        if (info && info.url && info.url.length > 0) {
            if (!this._resMap.has(info.url)) {
                if (isNoFoundCreate) {
                    let cache = new CacheInfo(info.url);
                    this._resMap.set(info.url, cache);
                } else {
                    return null;
                }
            }
            return this._resMap.get(info.url);
        }
    }

    /**
     * 设置资源为常驻资源
     * @param info 资源
     */
    retainAsset(info: Resource.Info) {
        if (info && info.data) {
            let cache = this._getCacheInfo(info);
            if (cache) {
                if (cache.retain) {
                    if (!info.retain && DEBUG) {
                        c3d.warn(`资源 : ${info.url} 已经被设置成常驻资源，不能改变其属性`);
                    }
                } else {
                    cache.retain = info.retain;
                }

                (<c3d.Asset>info.data).addRef();
                cache.refCount++;
                if (cache.retain) {
                    cache.refCount = 999999;
                }
            }
        }
    }

    /**
     * 释放资源，常驻资源不释放
     * @param info 资源
     * @returns 
     */
    releaseAsset(info: Resource.Info) {
        if (info && info.data) {
            let cache = this._getCacheInfo(info, false);
            if (cache) {
                if (cache.retain) {
                    //常驻内存中
                    return;
                }
                cache.refCount--;
                if (cache.refCount <= 0) {
                    this.remove(cache.url);
                }
            }
        }
    }

    /**
     * 移除资源计数引用和资源占用
     * @param url 资源url
     * @returns 
     */
    remove(url: string) {
        this._resMap.delete(url);
        //先删除精灵帧
        if (this._spriteFrameCaches.has(url)) {
            //先释放引用计数
            (<c3d.Asset>(this._spriteFrameCaches.get(url) as Resource.CacheData).data).decRef();
            this._spriteFrameCaches.delete(url);
            if (DEBUG) c3d.log(`remove remote sprite frames resource url : ${url}`);
        }

        let cache = this._caches.get(url);
        if (cache && cache.data instanceof c3d.sp.SkeletonData) {
            //这里面需要删除加载进去的三个文件缓存 
            this.remove(`${cache.info.url}.atlas`);
            this.remove(`${cache.info.url}.png`);
            this.remove(`${cache.info.url}.json`);
        }
        if (cache && cache.data instanceof c3d.Asset) {
            if (DEBUG) c3d.log(`释放加载的本地远程资源:${cache.info.url}`);
            cache.data.decRef();
            c3d.assetManager.releaseAsset(cache.data as c3d.Asset);
        }
        if (DEBUG) c3d.log(`remove remote cache url : ${url}`);
        return this._caches.delete(url);
    }

    print(delegate: CacheManagerPrintDelegate<Map<string, Resource.CacheData>, Map<string, CacheInfo>>) {
        delegate.printRemote(this._spriteFrameCaches, this._caches, this._resMap);
    }
}

export class CacheManager {
    private logTag = `[CacheManager]: `;

    /**包资源的缓存Map */
    private _bundles = new Map<string, ResourceCache>();
    /** */
    remoteCaches = new RemoteCaches();

    getBundleName(bundle: BUNDLE_TYPE) {
        return GM.assetMgr.bundl.getBundleName(bundle);
    }

    /**
     * 同步获取资源缓存，此接口不会检查资源的状态，只要建立了缓存，就会立即返回
     * @param bundle bundle名
     * @param path 资源路径
     * @param isCheck 是否检查资源有效性，当为ture时，会检查资源是否有效，如果有效直接返回，如果无效，则返回nll
     * @returns 
     */
    get(bundle: BUNDLE_TYPE, path: string, isCheck: boolean = true) {
        let bundleName = this.getBundleName(bundle);
        if (bundleName && this._bundles.has(bundleName)) {
            return (this._bundles.get(bundleName) as ResourceCache).get(path, isCheck);
        }
    }

    /**
     * 设置包的缓存
     * @param bundle bundle的名字
     * @param path 地址
     * @param data 数据
     */
    set(bundle: BUNDLE_TYPE, path: string, data: Resource.CacheData) {
        let bundleName = this.getBundleName(bundle);
        if (bundleName) {
            if (!this._bundles.has(bundleName)) {
                let cache = new ResourceCache(bundleName);
                cache.set(path, data);
                this._bundles.set(bundleName, cache);
            } else {
                (this._bundles.get(bundleName) as ResourceCache).set(path, data);
            }
        }
    }

    /**
     * 
     * @param bundle bundle
     * @param path path
     */
    remove(bundle: BUNDLE_TYPE, path: string) {
        let bundleName = this.getBundleName(bundle);
        if (bundleName && this._bundles.has(bundleName)) {
            return (this._bundles.get(bundleName) as ResourceCache).remove(path);
        }
        return false;
    }

    removeWithInfo(info: Resource.Info) {
        if (info) {
            if (info.data) {
                if (Array.isArray(info.data)) {
                    let isAllDelete = true;
                    for (let i = 0; i < info.data.length; i++) {
                        info.data[i].decRef();
                        if (info.data[i].refCount != 0) {
                            isAllDelete = false;
                        }
                    }
                    if (isAllDelete) {
                        this.remove(info.bundle, info.url);
                        return true;
                    }
                } else {
                    info.data.decRef();
                    if (info.data.refCount == 0) {
                        this.remove(info.bundle, info.url);
                        return true;
                    }
                }
            } else {
                c3d.error(`info.data is null , bundle : ${info.bundle} url : ${info.url}`);
            }
        } else {
            c3d.error(`info is null`);
        }
        return false;
    }

    removeBundle(bundle: BUNDLE_TYPE) {
        let bundleName = this.getBundleName(bundle);
        if (bundleName && this._bundles.has(bundleName)) {
            if (DEBUG) {
                c3d.log(`移除bundle cache : ${bundleName}`)
                let data = this._bundles.get(bundleName);
                this._bundles.forEach((value, key, origin) => {
                    if (value) {
                        value.removeUnuseCaches();
                    }
                });
                if (data && data.size > 0) {
                    c3d.error(`移除bundle ${bundleName} 还有未释放的缓存`);
                }
            }
            this._bundles.delete(bundleName);
        }
    }

    private _getGetCacheByAsyncArgs(): { url: string, type: typeof c3d.Asset, bundle: BUNDLE_TYPE } | null {
        if (arguments.length < 3) {
            if (DEBUG) c3d.error(`${this.logTag}参数传入有误，必须两个参数`);
            return null;
        }
        if (typeof arguments[0] != "string") {
            if (DEBUG) c3d.error(`${this.logTag}传入第一个参数有误,必须是string`);
            return null;
        }
        if (!c3d.js.isChildClassOf(arguments[1], c3d.Asset)) {
            if (DEBUG) c3d.error(`${this.logTag}传入的第二个参数有误,必须是cc.Asset的子类`);
            return null;
        }
        return { url: arguments[0], type: arguments[1], bundle: arguments[2] };
    }

    /**
     * 如果资源正在加载中，会等待资源加载完成后返回，否则直接返回null
     * @param url 
     * @param type 资源类型
     * @param bundle
     */
    getCache<T extends c3d.Asset>(url: string, type: { prototype: T }, bundle: BUNDLE_TYPE): Promise<T>;
    getCache() {
        let args = arguments;
        let me = this;
        return new Promise<any>((resolve) => {
            let _args = me._getGetCacheByAsyncArgs.apply(me, args as any);
            if (!_args) {
                resolve(null);
                return;
            }
            let cache = me.get(_args.bundle, _args.url);
            if (cache) {
                if (cache.isLoaded) {
                    //已经加载完成
                    if (_args.type) {
                        if (cache.data instanceof _args.type) {
                            resolve(cache.data);
                        } else {
                            if (DEBUG) c3d.error(`${this.logTag}传入类型:${c3d.js.getClassName(_args.type)}与资源实际类型: ${c3d.js.getClassName(cache.data as any)}不同 url : ${cache.info.url}`);
                            resolve(null);
                        }
                    } else {
                        resolve(cache.data);
                    }
                } else {  //加载中
                    cache.getCb.push(resolve);
                }
            } else {
                resolve(null);
            }
        });
    }

    /**
     * 异步获取资源，如果资源未加载，会加载完成后返回
     * @param url 
     * @param type 
     * @param bundle 
     */
    getCacheByAsync<T extends c3d.Asset>(url: string, type: { prototype: T }, bundle: BUNDLE_TYPE): Promise<T>;
    getCacheByAsync() {
        let me = this;
        let args = this._getGetCacheByAsyncArgs.apply(this, <any>arguments);
        return new Promise<any>((resolve) => {
            if (!args) {
                resolve(null);
                return;
            }
            me.getCache(args.url, args.type, args.bundle).then((data) => {
                args = args as { url: string, type: typeof c3d.Asset, bundle: BUNDLE_TYPE };
                if (data && data instanceof args.type) {
                    resolve(data);
                } else {
                    //加载资源
                    GM.assetMgr.load(args.bundle, args.url, args.type, <any>null, (cache) => {
                        args = args as { url: string, type: typeof c3d.Asset, bundle: BUNDLE_TYPE };
                        if (cache && cache.data && cache.data instanceof args.type) {
                            resolve(cache.data);
                        } else {
                            c3d.error(`${this.logTag}加载失败 : ${args.url}`);
                            resolve(null);
                        }
                    });
                }
            });
        });
    }

    getSpriteFrameByAsync(urls: string[], key: string, view: UIView, addExtraLoadResource: (view: UIView, info: Resource.Info) => void, bundle: BUNDLE_TYPE) {
        let me = this;
        return new Promise<{ url: string, spriteFrame: c3d.SpriteFrame | null, isTryReload?: boolean }>((resolve) => {
            let nIndex = 0;
            let getFun = (url: string) => {
                me.getCacheByAsync(url, c3d.SpriteAtlas, bundle).then((atlas) => {
                    let info = new Resource.Info({
                        url, type: c3d.SpriteAtlas, data: atlas, bundle
                    });
                    addExtraLoadResource(view, info);
                    if (atlas) {
                        let spriteFrame = atlas.getSpriteFrame(key);
                        if (spriteFrame) {
                            if (c3d.isValid(spriteFrame)) {
                                resolve({ url, spriteFrame });
                            } else {
                                //来到这里面，其实程序已经崩溃了，已经没什么意思，也不知道写这个有啥用，尽量安慰,哈哈哈
                                c3d.error(`精灵帧被释放，释放当前无法的图集资源 url ：${url} key : ${key}`);
                                GM.assetMgr.releaseAsset(info);
                                resolve({ url, spriteFrame: null, isTryReload: true });
                            }
                        } else {
                            nIndex++;
                            if (nIndex >= urls.length) {
                                resolve({ url, spriteFrame: null });
                            } else {
                                getFun(urls[nIndex]);
                            }
                        }
                    } else {
                        resolve({ url, spriteFrame: null });
                    }
                })
            };

            getFun(urls[nIndex]);
        });
    }

    print(delegate: CacheManagerPrintDelegate<Map<string, Resource.CacheData>, Map<string, CacheInfo>>) {
        this._bundles.forEach((value, key, originMap) => {
            value.print(delegate);
        });
        this.remoteCaches.print(delegate);
    }
}